package com.tools.web.work.qr;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.Writer;
import com.google.zxing.aztec.AztecWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.datamatrix.DataMatrixWriter;
import com.google.zxing.oned.*;
import com.google.zxing.pdf417.PDF417Writer;
import com.google.zxing.qrcode.QRCodeWriter;
import com.tools.common.exception.DataGenerationException;
import com.tools.common.exception.NotFoundDataException;
import com.tools.common.io.CharsetEnum;
import com.tools.common.object.Note;
import com.tools.common.object.string.VerifyKit;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;

/**
 * 条形码的实体类
 * */
@Note("条形码的实体类")
public class BarCode {

    @Note("条形码的规范枚举")
    private BarcodeFormat barcodeFormat = BarcodeFormat.CODE_128;

    @Note("不同规范的条形码对应的 Writer 对象，与 barcodeFormat 属性保持一致")
    private Writer barcodeWriter = private_newWriter(this.barcodeFormat);

    @Note("条形码内容")
    private String content;

    @Note("条形码内容的字符集的编码")
    private CharsetEnum contentCharset = CharsetEnum.UTF_8;

    @Note("条形码的最小高度")
    private int barcodeMinHeight = 70;

    @Note("默认的字体实例，采用懒加载初始化")
    private static volatile Font DEFAULT_FONT;

    @Note("底部文字")
    private Gloss bottom;

    @Note("左下角文字")
    private Gloss bottomLeft;

    @Note("右下角文字")
    private Gloss bottomRight;

    @Note("顶部文字")
    private Gloss top;

    @Note("左上角文字")
    private Gloss topLeft;

    @Note("右上角文字")
    private Gloss topRight;


    /* **************************************************************************************
     *
     *          内容
     *
     * **************************************************************************************
     * */

    public BarCode(String content) {
        this.setContent(content);
    }

    public static BarCode of(String content) {
        return new BarCode(content);
    }

    /* **************************************************************************************
     *
     *          Getter / Setter
     *
     * **************************************************************************************
     * */

    public BarcodeFormat getBarcodeFormat() {
        return barcodeFormat;
    }

    public void setBarcodeFormat(BarcodeFormat barcodeFormat) {
        if(barcodeFormat == null) throw new NullPointerException("不能设置 null 的条形码的规范类型实例");
        this.barcodeFormat = barcodeFormat;
        this.barcodeWriter = private_newWriter(this.barcodeFormat);
    }

    public Writer getBarcodeWriter() {
        return barcodeWriter;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        if(content == null || content.isEmpty()) {
            throw new NullPointerException("条形码的内容不能为空");
        }
        this.content = content;
    }

    public CharsetEnum getContentCharset() {
        return contentCharset;
    }

    public void setContentCharset(CharsetEnum contentCharset) {
        if(contentCharset == null) throw new NullPointerException("不能设置 null 的字符集");
        this.contentCharset = contentCharset;
    }

    public int getBarcodeMinHeight() {
        return barcodeMinHeight;
    }

    public void setBarcodeMinHeight(int barcodeMinHeight) {
        this.barcodeMinHeight = (barcodeMinHeight < 0) ? this.barcodeMinHeight : barcodeMinHeight;
    }


    public void setBottom(String text, int spacePX, Font font) {
        this.bottom = new Gloss(text, spacePX, font);
    }

    public void setTop(String text, int spacePX, Font font) {
        this.top = new Gloss(text, spacePX, font);
    }

    public void setTopLeft(String text, int offsetX, int offsetY, int spacePX, Font font) {
        this.topLeft = new Gloss(text, spacePX, font);
    }

    public void setTopRight(String text, int offsetX, int offsetY, int spacePX, Font font) {
        this.topRight = new Gloss(text, spacePX, font);
    }

    public void setBottomLeft(String text, int offsetX, int offsetY, int spacePX, Font font) {
        this.bottomLeft = new Gloss(text, spacePX, font);
    }

    public void setBottomRight(String text, int offsetX, int offsetY, int spacePX, Font font) {
        this.bottomRight = new Gloss(text, spacePX, font);
    }

    @Override
    public String toString() {
        return "BarCode{" +
                    "barcodeMinHeight=" + barcodeMinHeight +
                    ", contentCharset=" + contentCharset +
                    ", barcodeFormat=" + barcodeFormat +
                    ", top=" + top +
                    ", topLeft=" + topLeft +
                    ", topRight=" + topRight +
                    ", bottom=" + bottom +
                    ", bottomLeft=" + bottomLeft +
                    ", bottomRight=" + bottomRight +
                '}';

    }

    /* **************************************************************************************
     *
     *          链式调用
     *
     * **************************************************************************************
     * */

    public BarCode barcodeFormat(BarcodeFormat barcodeFormat) {
        this.setBarcodeFormat(barcodeFormat);
        return this;
    }

    public BarCode content(String content) {
        this.setContent(content);
        return this;
    }

    public BarCode contentCharset(CharsetEnum contentCharset) {
        this.setContentCharset(contentCharset);
        return this;
    }

    public BarCode barcodeMinHeight(int barcodeMinHeight) {
        this.setBarcodeMinHeight(barcodeMinHeight);
        return this;
    }

    public BarCode top(String text, int spacePX, Font font) {
        this.setTop(text, spacePX, font);
        return this;
    }

    public BarCode topLeft(String text, int offsetX, int offsetY, int spacePX, Font font) {
        this.setTopLeft(text, offsetX, offsetY, spacePX, font);
        return this;
    }

    public BarCode topRight(String text, int offsetX, int offsetY, int spacePX, Font font) {
        this.setTopRight(text, offsetX, offsetY, spacePX, font);
        return this;
    }

    public BarCode bottom(String text, int spacePX, Font font) {
        this.setBottom(text, spacePX, font);
        return this;
    }

    public BarCode bottomLeft(String text, int offsetX, int offsetY, int spacePX, Font font) {
        this.setBottomLeft(text, offsetX, offsetY, spacePX, font);
        return this;
    }

    public BarCode bottomRight(String text, int offsetX, int offsetY, int spacePX, Font font) {
        this.setBottomRight(text, offsetX, offsetY, spacePX, font);
        return this;
    }

    /* **************************************************************************************
     *
     *          生成条形码
     *
     * **************************************************************************************
     * */


    public BufferedImage out() {
        BufferedImage barcodeImage = this.private_out();
        int codeWidth = barcodeImage.getWidth();
        int codeHeight = barcodeImage.getHeight();
        int pictureWidth = codeWidth + 80;
        int pictureHeight = codeHeight + 40;
        BufferedImage picImage = new BufferedImage(pictureWidth, pictureHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = picImage.createGraphics();
        //抗锯齿
        private_setGraphics2D(g2d);
        // 设置条形码的背景图片颜色为白色
        private_setBackgroundColorAsWhite(g2d, picImage.getWidth(), picImage.getHeight());
        // 条形码默认居中显示
        int codeStartX = (pictureWidth - codeWidth) / 2;
        int codeStartY = (pictureHeight - codeHeight) / 2;
        // 画条形码到新的面板
        g2d.drawImage(barcodeImage, codeStartX, codeStartY, codeWidth, codeHeight, null);
        // 画文字到新的面板
        g2d.setColor(Color.BLACK);
        //底部居中文字
        if(bottom != null) {
            private_bottomCenterHandle(g2d, codeWidth, codeHeight, codeStartX, codeStartY);
        }
        //顶部居中文字
        if(top != null) {
            private_topCenterHandle(g2d, codeWidth, codeStartX, codeStartY);
        }
        //左上角文字
        if(topLeft != null) {
            private_topLeftHandle(g2d, codeStartX, codeStartY);
        }
        //右上角文字
        if(topRight != null) {
            private_topRightHandle(g2d, codeWidth, codeStartX, codeStartY);
        }
        //左下角文字
        if(bottomLeft != null) {
            private_bottomLeftHandle(g2d, codeHeight, codeStartX, codeStartY);
        }
        //右下角文字
        if(bottomRight != null) {
            private_bottomRightHandle(g2d, codeWidth, codeHeight, codeStartX, codeStartY);
        }
        g2d.dispose();
        picImage.flush();
        return picImage;
    }





    /* **************************************************************************************
     *
     *          私有逻辑
     *
     * **************************************************************************************
     * */

    @Note("根据枚举实例获取对应的 Writer 实例")
    private static Writer private_newWriter(BarcodeFormat barcodeFormat) {
        switch (barcodeFormat) {
            case CODE_128: return new Code128Writer();
            case PDF_417: return new PDF417Writer();
            case CODE_39: return new Code39Writer();
            case CODE_93: return new Code93Writer();
            case QR_CODE: return new QRCodeWriter();
            case AZTEC: return new AztecWriter();
            case CODABAR: return new CodaBarWriter();
            case DATA_MATRIX: return new DataMatrixWriter();
            case EAN_8: return new EAN8Writer();
            case EAN_13: return new EAN13Writer();
            case ITF: return new ITFWriter();
            case UPC_A: return new UPCAWriter();
            case UPC_E: return new UPCEWriter();
            default: throw new NotFoundDataException(barcodeFormat + " 不存在对应的 Writer 实例");
        }
    }

    @Note("生成条形码图片")
    private BufferedImage private_out() {
        Map<EncodeHintType, Object> hints = new HashMap<>(2);
        hints.put(EncodeHintType.CHARACTER_SET, contentCharset.getValue());
        if(VerifyKit.containsChinese(content)) {
            return this.private_outChineseByPDF417(hints);
        }
        try {
            BitMatrix bitMatrix = barcodeWriter.encode(
                    content, barcodeFormat, 0, barcodeMinHeight, hints);
            return MatrixToImageWriter.toBufferedImage(bitMatrix);
        } catch (Exception e) {
            throw new DataGenerationException(e);
        }
    }

    @Note("如果内容包含中文，就选择用 PDF417 规范生成条形码")
    private BufferedImage private_outChineseByPDF417(Map<EncodeHintType, Object> hints) {
        BarcodeFormat barcodeFormat = this.barcodeFormat;
        Writer barcodeWriter = this.barcodeWriter;
        if(barcodeFormat != BarcodeFormat.PDF_417 || !(barcodeWriter instanceof PDF417Writer)) {
            barcodeFormat = BarcodeFormat.PDF_417;
            barcodeWriter = private_newWriter(barcodeFormat);
        }
        try {
            BitMatrix bitMatrix = barcodeWriter.encode(
                    content, barcodeFormat, 0, barcodeMinHeight, hints);
            return MatrixToImageWriter.toBufferedImage(bitMatrix);
        } catch (Exception e) {
            throw new DataGenerationException(e);
        }
    }


    @Note("给条形码做抗锯齿操作")
    private static void private_setGraphics2D(Graphics2D g2d) {
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT);
        Stroke s = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
        g2d.setStroke(s);
    }

    @Note("设置条形码的背景图片颜色为白色")
    private static void private_setBackgroundColorAsWhite(Graphics2D g2d, int width, int height) {
        g2d.setColor(Color.WHITE);
        //填充整个屏幕
        g2d.fillRect(0, 0, width, height);
        //设置笔刷
        g2d.setColor(Color.BLACK);
    }

    @Note("校验并获取字体实例")
    private static Font private_checkAndGetFont(Font font) {
        return font == null
                ? private_lazyLoadDefaultFont()
                : font;
    }

    @Note("懒加载默认的字体实例")
    private static Font private_lazyLoadDefaultFont() {
        if(DEFAULT_FONT != null) return DEFAULT_FONT;
        synchronized (BarCode.class) {
            if(DEFAULT_FONT != null) return DEFAULT_FONT;
            DEFAULT_FONT = new Font("微软雅黑", Font.PLAIN, 12);
            return DEFAULT_FONT;
        }
    }

    @Note("给条形码加上底部居中文字")
    private void private_bottomCenterHandle(Graphics2D g2d, int barcodeWidth, int barcodeHeight, int codeStartX, int codeStartY) {
        String text = bottom.text;
        if(text == null || text.isEmpty()) return;
        Font font = bottom.font;
        g2d.setFont(font);
        int textWidth = g2d.getFontMetrics().stringWidth(text);
        int textStartX = codeStartX + (barcodeWidth - textWidth) / 2;
        int textStartY = codeStartY + barcodeHeight + font.getSize() + bottom.spacePX;
        g2d.drawString(text, textStartX, textStartY);
    }

    @Note("给条形码加上顶部居中文字")
    private void private_topCenterHandle(Graphics2D g2d, int barcodeWidth, int codeStartX, int codeStartY) {
        String text = top.text;
        if(text == null || text.isEmpty()) return;
        int textWidth = g2d.getFontMetrics().stringWidth(text);
        int textStartX = codeStartX + (barcodeWidth - textWidth) / 2;
        int textStartY = codeStartY -  top.spacePX;
        g2d.setFont(top.font);
        g2d.drawString(text, textStartX, textStartY);
    }

    @Note("给条形码加上左上角文字")
    private void private_topLeftHandle(Graphics2D g2d, int codeStartX, int codeStartY) {
        String text = topLeft.text;
        if(text == null || text.isEmpty()) return;
        int strStartY = codeStartY - topLeft.spacePX;
        g2d.setFont(topLeft.font);
        g2d.drawString(text, codeStartX, strStartY);
    }

    @Note("给条形码加上右上角文字")
    private void private_topRightHandle(Graphics2D g2d, int barcodeWidth, int codeStartX, int codeStartY) {
        String text = topRight.text;
        if(text == null || text.isEmpty()) return;
        int textWidth = g2d.getFontMetrics().stringWidth(text);
        int strStartX = codeStartX + barcodeWidth - textWidth;
        int strStartY = codeStartY - topRight.spacePX;
        g2d.drawString(text, strStartX, strStartY);
    }

    @Note("给条形码加上左下角文字")
    private void private_bottomLeftHandle(Graphics2D g2d, int codeHeight, int codeStartX, int codeStartY) {
        String text = bottomLeft.text;
        if(text == null || text.isEmpty()) return;
        int strStartY = codeStartY + codeHeight + bottomLeft.spacePX + bottomLeft.font.getSize();
        g2d.drawString(text, codeStartX, strStartY);
    }

    @Note("给条形码加上右下角文字")
    private void private_bottomRightHandle(Graphics2D g2d, int codeWidth, int codeHeight, int codeStartX, int codeStartY) {
        String text = bottomRight.text;
        if(text == null || text.isEmpty()) return;
        int textWidth = g2d.getFontMetrics().stringWidth(text);
        int strStartX = codeStartX + codeWidth - textWidth;
        int strStartY = codeStartY + codeHeight + bottomRight.spacePX + bottomRight.font.getSize();
        g2d.drawString(text, strStartX, strStartY);
    }

    /* **************************************************************************************
     *
     *          内部类
     *
     * **************************************************************************************
     * */

    @Note("条形码注释文字的实体类")
    private static class Gloss {

        @Note("文字内容")
        private final String text;

        @Note("文字与条形码之间的高度间隔，单位为像素 px")
        private final int spacePX;

        @Note("文字的样式、字型、大小的实例")
        private final Font font;


        private Gloss(String text, int spacePX, Font font) {
            this.text = text;
            this.spacePX = (spacePX < 0) ? 3 : spacePX;
            this.font = private_checkAndGetFont(font);
        }

        @Override
        public String toString() {
            return "{" +
                    "text=" + text +
                    ", spacePX=" + spacePX +
                    ", font=" + font +
                    '}';
        }
    }
}
